#键盘映射手柄V1.6
import time
import keyboard
import sys
import threading
import ctypes
import subprocess
import configparser
import win32event
import win32api
import winerror
import os
import traceback
from datetime import datetime
'''
手柄键值定义 
XUSB_GAMEPAD_DPAD_UP	0x0001	十字方向键上
XUSB_GAMEPAD_DPAD_DOWN	0x0002	十字方向键下
XUSB_GAMEPAD_DPAD_LEFT	0x0004	十字方向键左
XUSB_GAMEPAD_DPAD_RIGHT	0x0008	十字方向键右
XUSB_GAMEPAD_START	0x0010	Start 按钮
XUSB_GAMEPAD_BACK	0x0020	Back 按钮
XUSB_GAMEPAD_LEFT_THUMB	0x0040	左摇杆按下（L3）
XUSB_GAMEPAD_RIGHT_THUMB	0x0080	右摇杆按下（R3）
XUSB_GAMEPAD_LEFT_SHOULDER	0x0100	LB
XUSB_GAMEPAD_RIGHT_SHOULDER	0x0200	RB
XUSB_GAMEPAD_GUIDE	0x0400	Xbox 中央 LOGO 按钮
XUSB_GAMEPAD_A	0x1000	A 按钮
XUSB_GAMEPAD_B	0x2000	B 按钮
XUSB_GAMEPAD_X	0x4000	X 按钮
XUSB_GAMEPAD_Y	0x8000	Y 按钮
'''
# 打包工具
#python -m auto_py_to_exe

try:
    #暂时不需要防止二次运行和管理员权限

    #raise ValueError("模拟一个错误")  # 示例报错

    # # ------- 创建命名互斥锁（防止二次运行） -------
    # # 防止程序重复运行
    # mutex_name = "vgamepad_single_instance_lock"
    # hMutex = win32event.CreateMutex(None, False, mutex_name)
    # last_error = win32api.GetLastError()
    #
    # if last_error == winerror.ERROR_ALREADY_EXISTS:
    #     ctypes.windll.user32.MessageBoxW(0, "程序已在运行中！", "提示", 0x40)
    #     sys.exit(0)

    # def is_admin():
    #     """
    #     检查是否有管理员权限
    #     """
    #     try:
    #         return ctypes.windll.shell32.IsUserAnAdmin()
    #     except:
    #         return False
    #
    # if not is_admin():
    #     print("当前权限非管理员，正在尝试以管理员权限重新运行脚本...")
    #     ctypes.windll.shell32.ShellExecuteW(
    #         None, "runas", sys.executable, " ".join(sys.argv), None, 1
    #     )
    #     sys.exit()
    # else:
    #     print("已拥有管理员权限，开始初始化...")


    def is_vigem_driver_installed():#检测是否安装 ViGEm 驱动，
        try:
            output = subprocess.check_output(["sc", "query", "ViGEmBus"], stderr=subprocess.STDOUT, text=True)#命令行指令，用于查询名为 ViGEmBus 的系统服务的状态。
            return "STATE" in output
        except subprocess.CalledProcessError:
            return False

    if not is_vigem_driver_installed():
        print("未检测到 ViGEm 驱动，请安装程序目录下的ViGEmBus_1.22.0_x64_x86_arm64文件或前往 https://vigem.org 下载并安装 ViGEmBus。")
        print("请直接关闭程序。")
        while True:
            time.sleep(1)
    else:
        print("ViGEm 驱动已安装。")
    import vgamepad as vg

    exit_event = threading.Event()#创建退出标记事件

    # 映射是否启用的标志
    mapping_enabled = True

    # 创建一个 Xbox 360 手柄虚拟对象
    gamepad = vg.VX360Gamepad()

    # 摇杆模拟范围，最大值和中间值
    AXIS_MAX = 32767
    AXIS_MIN = -32768
    AXIS_CENTER = 0

    # 读取配置
    # 使用 inline_comment_prefixes 参数 会把分号后的内容当作注释剔除
    config = configparser.ConfigParser(
    inline_comment_prefixes=(';',),
        allow_no_value=True
    )
    #读取配置ini文件
    config.read("./_internal/config/config.ini", encoding="utf-8-sig")

    key_map = config["button_mapping"]

    keyboard_to_button = {}

    # 构建映射字典（这里读取的是按键映射，摇杆和扳机映射在其他地方）
    for key, value in key_map.items():
        value_upper = value.upper()
        if value_upper in ["LEFT_TRIGGER", "RIGHT_TRIGGER"]:
            keyboard_to_button[key] = value_upper  # 使用字符串标记触发器
        else:
            try:
                button_enum = getattr(vg.XUSB_BUTTON, "XUSB_GAMEPAD_" + value_upper)
                keyboard_to_button[key] = button_enum
            except AttributeError:
                print(f"警告：未知手柄按键映射 '{value}'，键 '{key}' 将被忽略。")

    # 扳机设置
    linear = config.getboolean("trigger_settings", "trigger_linear", fallback=False)#是否使用线性映射
    lt_inc_rate = config.getint("trigger_settings", "lt_trigger_inc", fallback=10)#左扳机每次更新增加数值
    rt_inc_rate = config.getint("trigger_settings", "rt_trigger_inc", fallback=10)#右扳机每次更新增加数值
    lt_dec_rate = config.getint("trigger_settings", "lt_trigger_dec", fallback=15)#左扳机每次更新减少数值
    rt_dec_rate = config.getint("trigger_settings", "rt_trigger_dec", fallback=15)#右扳机每次更新减少数值

    # print(rt_dec_rate)

    # 线性扳机，最大值和最小值
    LT_TRI_MAX = min(255,config.getint("trigger_settings", "LT_TRI_MAX", fallback=255))
    RT_TRI_MAX = min(255,config.getint("trigger_settings", "RT_TRI_MAX", fallback=255))
    TRI_UPTIME = config.getfloat("trigger_settings", "TRI_INTERVAL", fallback=0.02)#扳机数据更新时间

    TRI_MAX = 255 #非线性映射情况下，扳机键按下对应值
    TRI_MIN = 0   #非线性映射情况下，扳机键抬起对应值

    lt_val = 0 #扳机初始值
    rt_val = 0 #扳机初始值
    lt_holding = False #标记下一次更新扳机数据时是增加还是减少
    rt_holding = False

    # --- 读取 pause/start 按键 ---
    pause_key = config.get("settings", "pause_mapping", fallback="").lower()
    start_key = config.get("settings", "start_mapping", fallback="").lower()

    # --- 读取摇杆设置 ---
    stick_cfg = config["rocker_settings"]
    lt_stick_enable = config.getboolean("rocker_settings", "LEFT_ROCKER_EN", fallback=False)
    rt_stick_enable = config.getboolean("rocker_settings", "RIGHT_ROCKER_EN", fallback=False)

    lt_stick_keys = {
        "up":    stick_cfg.get("lt_rocker_up", fallback="t").lower(),
        "down":  stick_cfg.get("lt_rocker_down", fallback="g").lower(),
        "left":  stick_cfg.get("lt_rocker_left", fallback="f").lower(),
        "right": stick_cfg.get("lt_rocker_right", fallback="h").lower(),
    }

    rt_stick_keys = {
        "up": stick_cfg.get("rt_rocker_up", fallback="up").strip().lower(),
        "down": stick_cfg.get("rt_rocker_down", fallback="down").strip().lower(),
        "left": stick_cfg.get("rt_rocker_left", fallback="left").strip().lower(),
        "right": stick_cfg.get("rt_rocker_right", fallback="right").strip().lower(),
    }

    # 范围
    LT_X_MAX = config.getint("rocker_settings", "lt_rocker_x_max", fallback=32767)
    LT_X_MIN = config.getint("rocker_settings", "lt_rocker_x_min", fallback=-32768)
    LT_Y_MAX = config.getint("rocker_settings", "lt_rocker_y_max", fallback=32767)
    LT_Y_MIN = config.getint("rocker_settings", "lt_rocker_y_min", fallback=-32768)

    RT_X_MAX = config.getint("rocker_settings", "rt_rocker_x_max", fallback=32767)
    RT_X_MIN = config.getint("rocker_settings", "rt_rocker_x_min", fallback=-32768)
    RT_Y_MAX = config.getint("rocker_settings", "rt_rocker_y_max", fallback=32767)
    RT_Y_MIN = config.getint("rocker_settings", "rt_rocker_y_min", fallback=-32768)

    # 速率 & 间隔
    STICK_INC   = config.getint("rocker_settings", "inc_rate", fallback=3000)
    STICK_DEC   = config.getint("rocker_settings", "dec_rate", fallback=3000)
    STICK_INTVL = config.getfloat("rocker_settings", "ROCKER_TRI_INTERVAL", fallback=0.02)

    # 左摇杆当前值 & 按键保持标志
    lt_stick_x = 0
    lt_stick_y = 0
    lt_hold_up   = False
    lt_hold_down = False
    lt_hold_left = False
    lt_hold_right= False

    # 右摇杆当前值 & 按键保持标志
    rt_stick_x = 0
    rt_stick_y = 0
    rt_hold_up = False
    rt_hold_down = False
    rt_hold_left = False
    rt_hold_right = False

    def rt_stick_worker():
        global rt_stick_x, rt_stick_y
        while True:
            if not rt_stick_enable:
                # 不启用时强制回中
                rt_stick_x = 0; rt_stick_y = 0
            else:
                # X 轴
                if rt_hold_left and not rt_hold_right:
                    rt_stick_x = max(RT_X_MIN, rt_stick_x - STICK_INC)
                elif rt_hold_right and not lt_hold_left:
                    rt_stick_x = min(RT_X_MAX, rt_stick_x + STICK_INC)
                else:
                    # 回中
                    if rt_stick_x > 0:
                        rt_stick_x = max(0, rt_stick_x - STICK_DEC)
                    elif rt_stick_x < 0:
                        rt_stick_x = min(0, rt_stick_x + STICK_DEC)

                # Y 轴
                if rt_hold_up and not rt_hold_down:
                    rt_stick_y = min(RT_Y_MAX, rt_stick_y + STICK_INC)
                elif rt_hold_down and not rt_hold_up:
                    rt_stick_y = max(RT_Y_MIN, rt_stick_y - STICK_INC)
                else:
                    if rt_stick_y > 0:
                        rt_stick_y = max(0, rt_stick_y - STICK_DEC)
                    elif rt_stick_y < 0:
                        rt_stick_y = min(0, rt_stick_y + STICK_DEC)

            # 发送到手柄
            gamepad.right_joystick(x_value=rt_stick_x, y_value=rt_stick_y)
            gamepad.update()
            time.sleep(STICK_INTVL)
    if rt_stick_enable:
        # 启动守护线程
        threading.Thread(target=rt_stick_worker, daemon=True).start()

    def lt_stick_worker():
        global lt_stick_x, lt_stick_y
        while True:
            if not lt_stick_enable:
                # 不启用时强制回中
                lt_stick_x = 0; lt_stick_y = 0
            else:
                # X 轴
                if lt_hold_left and not lt_hold_right:
                    lt_stick_x = max(LT_X_MIN, lt_stick_x - STICK_INC)
                elif lt_hold_right and not lt_hold_left:
                    lt_stick_x = min(LT_X_MAX, lt_stick_x + STICK_INC)
                else:
                    # 回中
                    if lt_stick_x > 0:
                        lt_stick_x = max(0, lt_stick_x - STICK_DEC)
                    elif lt_stick_x < 0:
                        lt_stick_x = min(0, lt_stick_x + STICK_DEC)

                # Y 轴
                if lt_hold_up and not lt_hold_down:
                    lt_stick_y = min(LT_Y_MAX, lt_stick_y + STICK_INC)
                elif lt_hold_down and not lt_hold_up:
                    lt_stick_y = max(LT_Y_MIN, lt_stick_y - STICK_INC)
                else:
                    if lt_stick_y > 0:
                        lt_stick_y = max(0, lt_stick_y - STICK_DEC)
                    elif lt_stick_y < 0:
                        lt_stick_y = min(0, lt_stick_y + STICK_DEC)

            # 发送到手柄
            gamepad.left_joystick(x_value=lt_stick_x, y_value=lt_stick_y)
            gamepad.update()
            time.sleep(STICK_INTVL)
    if lt_stick_enable:
        # 启动守护线程
        threading.Thread(target=lt_stick_worker, daemon=True).start()

    def trigger_worker():
        """
        这个线程全程运行，根据 lt_holding/rt_holding 的值
        单线程地对 lt_val/rt_val 做增减，并 update 手柄。
        """
        global lt_val, rt_val
        while True:
            # 左扳机
            if lt_holding:
                lt_val = min(LT_TRI_MAX, lt_val + lt_inc_rate)
            else:
                lt_val = max(TRI_MIN, lt_val - lt_dec_rate)
            gamepad.left_trigger(lt_val)

            # 右扳机
            if rt_holding:
                rt_val = min(RT_TRI_MAX, rt_val + rt_inc_rate)
            else:
                rt_val = max(TRI_MIN, rt_val - rt_dec_rate)
            gamepad.right_trigger(rt_val)

            # 推送到手柄
            gamepad.update()
            time.sleep(TRI_UPTIME)
    if linear:
        # 启动后台线程（守护线程，随主程序退出）
        threading.Thread(target=trigger_worker, daemon=True).start()

    # --- 4. 事件回调 ---
    def on_key(event):
        global mapping_enabled
        # 跳过没有 name 的事件
        if not hasattr(event, "name") or event.name is None:
            return True
        key = event.name.lower()
        # print(key)
        # print(rt_stick_keys["up"])
        if event.event_type == keyboard.KEY_DOWN:
            # print(key)
            # —— 1. 先看是不是 pause/start 切换键 ——
            if key == pause_key:
                mapping_enabled = False
                print("已暂停映射")
                return False # 拦截，不继续映射
            if key == start_key:
                mapping_enabled = True
                print("已启动映射")
                return False

            # —— 2. 再看是否整体映射已被暂停 ——
            if not mapping_enabled:
                return True # 既不做映射，也不调用 press_button/release

            # 1. 摇杆按键分流
            if lt_stick_enable and key in lt_stick_keys.values():
                # print(key)
                # 哪个方向就置 True
                if key == lt_stick_keys["up"]:
                    global lt_hold_up
                    lt_hold_up = True
                elif key == lt_stick_keys["down"]:
                    global lt_hold_down
                    lt_hold_down = True
                elif key == lt_stick_keys["left"]:
                    global lt_hold_left
                    lt_hold_left = True
                elif key == lt_stick_keys["right"]:
                    global lt_hold_right
                    lt_hold_right = True
                return False  # 分流后不要往下执行到普通按钮或扳机
            # print(key)
            if rt_stick_enable and key in rt_stick_keys.values():
                # print(key)
                # 哪个方向就置 True
                if key == rt_stick_keys["up"]:
                    global rt_hold_up
                    rt_hold_up = True
                elif key == rt_stick_keys["down"]:
                    global rt_hold_down
                    rt_hold_down = True
                elif key == rt_stick_keys["left"]:
                    global rt_hold_left
                    rt_hold_left = True
                elif key == rt_stick_keys["right"]:
                    global rt_hold_right
                    rt_hold_right = True
                return False   # 分流后不要往下执行到普通按钮或扳机

            # 2. 扳机按键分流（u/o）
            if key in ("u", "o") and key in keyboard_to_button:
                # 如果是线性模式，只改 holding 标志
                if linear:
                    if key == "u":
                        global lt_holding
                        lt_holding = True
                    else:
                        global rt_holding
                        rt_holding = True
                else:
                    # 瞬时模式直接设满
                    if key == "u":
                        gamepad.left_trigger(TRI_MAX)
                    else:
                        gamepad.right_trigger(TRI_MAX)
                    gamepad.update()
                return False   # 分流后不要继续往下

            # 3. 普通按钮/方向键按下
            if key in keyboard_to_button:
                btn = keyboard_to_button[key]
                gamepad.press_button(btn)
                gamepad.update()
                return False
        return True

    def on_key_release(event):
        # 跳过没有 name 的事件
        if not hasattr(event, "name") or event.name is None:
            return
        key = event.name.lower()
        # 1. 摇杆松开
        if lt_stick_enable and key in lt_stick_keys.values():
            if key == lt_stick_keys["up"]:
                global lt_hold_up
                lt_hold_up = False
            elif key == lt_stick_keys["down"]:
                global lt_hold_down
                lt_hold_down = False
            elif key == lt_stick_keys["left"]:
                global lt_hold_left
                lt_hold_left = False
            elif key == lt_stick_keys["right"]:
                global lt_hold_right
                lt_hold_right = False
            return

        if rt_stick_enable and key in rt_stick_keys.values():
            if key == rt_stick_keys["up"]:
                global rt_hold_up
                rt_hold_up = False
            elif key == rt_stick_keys["down"]:
                global rt_hold_down
                rt_hold_down = False
            elif key == rt_stick_keys["left"]:
                global rt_hold_left
                rt_hold_left = False
            elif key == rt_stick_keys["right"]:
                global rt_hold_right
                rt_hold_right = False
            return

        # 2. 扳机松开
        if key in ("u", "o") and key in keyboard_to_button:
            if linear:
                if key == "u":
                    global lt_holding
                    lt_holding = False
                else:
                    global rt_holding
                    rt_holding = False
            else:
                if key == "u":
                    gamepad.left_trigger(TRI_MIN)
                else:
                    gamepad.right_trigger(TRI_MIN)
                gamepad.update()
            return

        # 3. 普通按钮/方向键松开
        if key in keyboard_to_button:
            btn = keyboard_to_button[key]
            gamepad.release_button(btn)
            gamepad.update()
            return
        if key == "esc":
            exit_event.set()#退出标志位
            keyboard.unhook_all()#关闭所有监听


    #keyboard.on_press(on_key,suppress=1)
    # 安装全局钩子，只根据返回值决定是否 suppress
    keyboard.hook(on_key, suppress=True)
    keyboard.on_release(on_key_release,suppress=0)
    # keyboard.wait()
    print("开始初始化完成开始映射...")
    print("\r\n")
    print("如果要映射多个手柄请复制程序文件夹并修改配置文件，确保没有重复的按键定义，否则重复的按键定义只有一个手柄会响应。")
    print("\r\n")
    print("改键位请修改_internal/config/config.ini 文件。")
    print("\r\n")
    print("按ESC关闭映射，映射期间使用到的按键无法输出按键值，只会输出对应手柄数据。")
    # 主循环等待退出信号
    while not exit_event.is_set():
        time.sleep(0.01)

    print("退出完成")
    sys.exit(0)
except Exception as e:
    # 获取当前目录
    if getattr(sys, 'frozen', False):
        base_dir = os.path.dirname(sys.executable)
    else:
        base_dir = os.path.dirname(os.path.abspath(__file__))

    # 创建 logs 文件夹
    log_dir = os.path.join(base_dir, "logs")
    os.makedirs(log_dir, exist_ok=True)

    # 创建日志文件名
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    log_path = os.path.join(log_dir, f"{timestamp}.log")

    # 写入日志
    with open(log_path, "w", encoding="utf-8") as f:
        f.write(f"时间：{timestamp}\n")
        f.write(f"错误类型：{type(e).__name__}\n")
        f.write(f"错误信息：{e}\n\n")
        f.write("详细回溯：\n")
        traceback.print_exc(file=f)

    print(f"程序发生错误，已保存到日志文件：{log_path}")
    traceback.print_exc()